#include <time.h>
#include "main_aux.h"
#include "integrator.h"
// ------------------------------------------------------------------------------------------- //
typedef  void (*flagprocessor)(const unsigned int argc, const char * argv[], unsigned int &index);
// ------------------------------------------------------------------------------------------- //
// remember to initialize the model to at least level 1 if required for
// options that read data from the model
// ------------------------------------------------------------------------------------------- //
bool nohup_flag = false;

void flag_runparser(const unsigned int argc, const char * argv[], unsigned int &index){
  model_class & model = getModel(); // use global func
  model.compile();
  model.init_io();
  ParserX & P = getParser();
  qstream ostream(stdout);
  string instream;
  printf("Starting parser:\nType an expression to evaluate or 'exit' to quit.\n");
  while(true){
    // use readline c api
    char * linebufr = readline("< ");
    if(linebufr && strlen(linebufr) > 0){
      instream = string(linebufr);
      free(linebufr);
      if(instream == "exit") break;
      clock_t stime = clock();
      if(instream != "!" && instream.size() > 0 ){
         token_list expr;
         extract_words(instream, expr);
         P.SetExpr(expr);
      }
      add_history(instream.c_str());
      try{
        const IValue & dval = P.Eval();
        double tdiff = (clock()-stime);
        ostream.printf("  ");
        if( dval.IsString() ){ ostream << string("\""); }
        ostream << dval;
        if( dval.IsString() ) ostream << string("\"");
        ostream.printf("\n");
      }catch(const ParserError & e){
        fprintf(stderr, "Error: %s\n", e.what());
      }
    }
  }
  clear_history();
};
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
void flag_parse(const unsigned int argc, const char * argv[], unsigned int &index){
  model_class & mo = getModel();
  mo.compile();
  // takes one 1 argument at least
  if(index +1 >= argc || argv[index+1][0] == '-'){
    fprintf(stderr, "Flag -eval takes at least 1 argument.\n");
    return;
  }
  index++;
  qstream ostream(stdout);
  while(true){
    const string exprStr( argv[index] );
    token_list expr;
    extract_words(exprStr, expr);
    ParserX & P = getParser();
    P.SetExpr(expr);
    try{
      const IValue & fval = P.Eval();
      ostream << fval;
      ostream.printf("\n");    
    }catch(const ParserError & e){
      fprintf(stderr, "Error: %s\n", e.what());
      return;
    }
    index++;
    if(index >= argc){ return; }
    if(argv[index][0] == '-'){ index--; return; }
  }
};
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
void flag_printprofiles(const unsigned int args, const char * argv[], unsigned int& index){
  model_class & mo = getModel();
  mo.compile();
  printMOProfiles(mo, qstream(stdout) );
};
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
void flag_printfields(const unsigned int args, const char * argv[], unsigned int & index){
  model_class & mo = getModel();
  mo.compile();
  printMOfields(mo, stdout);
};
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
void flag_printvariables(const unsigned int args, const char * argv[], unsigned int & index){
  model_class & mo = getModel();
  mo.compile();
  printMOvariables(mo, stdout);
};
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
void flag_printfunctions(const unsigned int args, const char * argv[], unsigned int & index){
  model_class & mo = getModel();
  mo.compile();
  printMOfunctions(mo, stdout);
};
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
void flag_printmacros(const unsigned int args, const char * argv[], unsigned int & index){
  model_class & mo = getModel();
  mo.compile();
  printMOMacros(mo, stdout); 
};
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
void flag_printparams(const unsigned int args, const char * argv[], unsigned int& index){
  model_class & mo = getModel();
  mo.compile();
  printMOParams(mo, stdout);
};
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
bool getoptvalue( string & optvalue, const vector<token> & argoptions, size_t & iter){
  const size_t asize = argoptions.size();
  if(iter + 1 >= asize){
    fprintf(stderr, "Error: Expected '=' after option statement.\n");
    return false;
  }
  iter++;
  if( ( argoptions[iter].word != "=" ) ){
    fprintf(stderr, "Error: Expected '=' after option statement.\n");
    return false;
  }
  iter++;
  while(iter < asize){
    if(argoptions[iter].word == ","){
      return true;
    }
    optvalue += argoptions[iter].word;
    iter++;
  }
  return true;
};
//. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
struct staticpart_opts {
  vector< pair<string, string> > soptions;
  REAL rcoeff;
  REAL scoeff;
  staticpart_opts() {
    soptions.push_back( pair<string, string>( "filename", "static_coeffs.mat" ));
    soptions.push_back( pair<string, string>( "mode", "new" ));
    soptions.push_back( pair<string, string>( "norun", "" ));
    soptions.push_back( pair<string, string>( "setallunity", "" ) );
    soptions.push_back( pair<string, string>( "kpqintegrand", "" ));
    soptions.push_back( pair<string, string>( "asymkpqintegrand", "" ));
    soptions.push_back( pair<string, string>( "R_coeff", "" ));
    soptions.push_back( pair<string, string>( "S_coeff", "" ));
    soptions.push_back( pair<string, string>( "format", "matlab" ));
    soptions.push_back( pair<string, string>( "customAhat", "" ));
    soptions.push_back( pair<string, string>( "customQ", "" ));
    soptions.push_back( pair<string, string>( "customP", "" ));
    rcoeff = 1;
    scoeff = 1;
  };
  // . . .  . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 
  int getoptionindex(const string & optionname) const{
    for(size_t i = 0; i < soptions.size(); i++){
      if(soptions[i].first == optionname) return i;
    }
    return -1;
  }
  // . . .  . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 
  bool isoptionset(const string & optionname) const{
    int index = this->getoptionindex(optionname);
    if(index >= 0){
      if(soptions[index].second.size() > 0) return true;
      return false;
    }
    throw cgcmexception("Fatal error: Could not find requested option. Error in function staticpart_opts::isoptionset\n", -20 ); 
  }
  // . . .  . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 
  const string & getoptionvalue(const string & optionname) const{
    int index = this->getoptionindex(optionname);
    if(index >= 0) return soptions[index].second;
    throw cgcmexception("Fatal error: Could not find requested option. Error in function staticpart_opts::getoptionvalue\n", -20 ); 
  }
  // . . .  . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
  void setoption(const string & optionname, const string & value){
    int index = this->getoptionindex(optionname);
    if(index >= 0){  soptions[index].second = value; return; } 
    throw cgcmexception("Fatal error: Could not find requested option. Error in function staticpart_opts::getoptionvalue\n", -20 ); 
  } 
  // . . .  . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 
  bool loadoptions(){
    if( this-> isoptionset("setallunity") ){
      if(! (this->isoptionset("kpqintegrand") ) )     this->setoption("kpqintegrand", "1");
      if(! (this->isoptionset("asymkpqintegrand") ) ) this->setoption("asymkpqintegrand", "1");
      if(! (this->isoptionset("R_AxAintegrand") ) )   this->setoption("R_AxAintegrand", "1");
      if(! (this->isoptionset("S_AxAintegrand") ) )   this->setoption("S_AxAintegrand", "1");
    }
    if(this-> isoptionset("kpqintegrand")){
      const string & expr = this->getoptionvalue("kpqintegrand");
      custom_func_container & kpqc = *(get_custom_func_container(4));
      try{
        token_list expression;
        extract_words( expr, expression );
        kpqc.assignexpr( expression );
      }catch( const ParserError & e ){
        fprintf(stderr, "Error '%s'.\n", e.what());
        fprintf(stderr, "Evaluation of expression for option '%s' failed.\n", "kpqintegrand");
        return false;
      }
      getModel().printf( "Using custom symmetric kpq integrand with value = (%s).\n",
          expr.c_str() ); 
    }
    if(this->isoptionset("customP")){
      custom_func_container & pcust = *(get_custom_func_container(7));
      try{  
        token_list expr;
        extract_words( this->getoptionvalue("customP"), expr );
        pcust.assignexpr( expr );
      }catch( const ParserError & e){
        fprintf(stderr, "Error: '%s'.\n", e.what());
        fprintf(stderr, "Evaluation of expression for option 'customP' failed.\n");
        return false;
      }
    }
    if(this->isoptionset("customQ")){
      custom_func_container & qcust = *(get_custom_func_container(6));
      try{
        token_list expr;
        extract_words( this->getoptionvalue("customQ"), expr );
        qcust.assignexpr( expr );
      }catch( const ParserError & e){
        fprintf(stderr, "Error: '%s'.\n", e.what());
        fprintf(stderr, "Evaluation of expression for option 'customQ' failed.\n");
        return false;
      }
    }
    if(this->isoptionset("customAhat")){
      custom_func_container & acust = *(get_custom_func_container(5));
      try{
        token_list expr; 
        extract_words( this->getoptionvalue("customAhat"), expr );
        acust.assignexpr( expr );
      }catch( const ParserError & e ){
        fprintf(stderr, "Error: '%s'.\n", e.what());
        fprintf(stderr, "Evaluation of expression for option 'customAhat' failed.\n");
        return false;
      }
    }
    if(this->isoptionset("asymkpqintegrand")){
      // set the asymmetric part of the integrand using a custom parser function
      const char * optionname[1] = { "asymkpqintegrand" };
      custom_func_container & akpqc = *(get_custom_func_container(1));
      try{
        token_list expr;
        extract_words( this->getoptionvalue("asymkpqintegrand"), expr );
        akpqc.assignexpr( expr );
      }catch( const ParserError & e ){
        fprintf(stderr, "Error: '%s'.", e.what());
        fprintf(stderr, "Evaluation of expression for option '%s' failed.\n", optionname[0]);
        return false;
      }
      getModel().printf( "Using custom asymmetric kpq integrand with value = (%s).\n", this->getoptionvalue("asymkpqintegrand").c_str());
    }
    const string coeff_options_list[2] = { "R_coeff", "S_coeff" };
    REAL * coeffs [2] = { &(this->rcoeff), &(this->scoeff) };
    for(int ii = 0; ii < 2; ii ++ ){
      const string & coeff_token= coeff_options_list[ii];
      if( this->isoptionset(coeff_token) ){
        token_list expr;
        extract_words(this->getoptionvalue(coeff_token), expr);
        ParserX P(pckNONE);
        getModel().assigndiagfunctions(P);
        P.SetExpr(expr);
        try{
          *(coeffs[ii]) = static_cast<REAL>(P.Eval().GetFloat());
        }catch(const ParserError & e){ 
          fprintf(stderr, "Error: Evalution of '%s' must be a constant.\nParser returned message: '%s'\n", 
              coeff_token.c_str(), e.what() ); 
          return false;
        };
      }
    }
    return true;
  }; 
}; // end struct staticpart_opts
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
void parseoptions(  staticpart_opts & options, const unsigned int argc, const char * argv[], unsigned int & index){
  index++;
  string combinedargs;
  while( index < argc ){
    if( argv[index][0] == '-' ){
      index --;
      break;
    }
    combinedargs += string(" ") + string(argv[index]);
    index++;
  }
  // once the args are combined into a single string, we tokenize
  vector<token> argoptions;
  preprocessor::tokenizestring(combinedargs, argoptions);
  // now we go throw and do matching
  for(size_t i = 0; i < argoptions.size(); i++){
    const string & optname = argoptions[i].word;
    int optnumber = -1;
    for(size_t k = 0; k < options.soptions.size(); k++){
      if( optname == options.soptions[k].first ){
        optnumber = k;
        break;
      }
    }
    if(optnumber < 0){
      fprintf(stderr, "Unrecognized option '%s' given to -computestaticpart flag.\n", optname.c_str());
      fprintf(stderr, "\tAllowed options are: \n");
      for(size_t x = 0; x < options.soptions.size(); x++){
        fprintf(stderr, "\t\t%s\n", options.soptions[x].first.c_str() );
      }
      throw cgcmexception("Error in flag specification .... ", -12);
    }
    // we need format to be optionname = value semicolon
    string optvalue;
    getoptvalue(optvalue, argoptions, i);
    if(isStringString(optvalue.c_str())){
      optvalue = removequotes(optvalue);
    }
    options.soptions[optnumber].second = optvalue;
  }
};
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
void flag_computestaticpart(const unsigned int argc, const char * argv[], unsigned int & index){
  model_class & mo = getModel();
  mo.definemacro(string("__static_integration__"), string(""));
  mo.compile();
  mo.init_io();
  mo.init_field_maps();
  staticpart_opts options;
  parseoptions(options, argc, argv, index);
  if(!options.loadoptions()){
    throw cgcmexception("Error in assigning options for static integration.", -25);
  }
  bool usecustomkpqintegrand = options.isoptionset("kpqintegrand") || (*get_custom_func_container(4)).isdefined();
  bool usecustomasymkpqintegrand = options.isoptionset("asymkpqintegrand") || (*get_custom_func_container(1)).isdefined();
  bool usecustomAhat = options.isoptionset("customAhat") || (*get_custom_func_container(5)).isdefined();
  bool usecustomQ = options.isoptionset("customQ") || (*get_custom_func_container(6)).isdefined();
  bool usecustomP = options.isoptionset("customP") || (*get_custom_func_container(7)).isdefined();

  void ( * kpqintegrand ) ( REAL &, const REAL &, const REAL &, const REAL & ) = 
    ( ! usecustomkpqintegrand  ) ? computecrossprodmagnitude : custom_symcomputeKPQintegrand;
  qgcomplex ( * asymkpqintegrand ) ( const REAL &, const REAL &, const REAL &, const INDEX & , const INDEX & ) 
    = usecustomasymkpqintegrand ? custom_asymcomputeKPQintegrand : compute_asymKPQintegrand;
  REAL ( * computeA ) ( const model_class & mo, const REAL &, const REAL &, const REAL &, const rank3 & ) = 
    usecustomAhat ? customAhatfunc : NULL;
  REAL ( * computeQ ) (const model_class & mo, const REAL &, const rank2 &) = 
    usecustomQ ? customQ : computeQ_def;
  bool ( * computeP ) ( const rank3 & ) = usecustomP ? customP : computeP_def;
  // create custom static_containers ...
  static_container * Rs = NULL;
  static_container * Ss = NULL;
  map_container<REAL, 6> * Amap = NULL;
  map_container<REAL, 3> * Qmap = NULL;
  map_container<REAL, 3> * QImap = NULL;

  bool overwrite = options.getoptionvalue("mode") == "overwrite";
  if( ! overwrite ) { // prob file to make sure it does not exist already
    const char * fname = options.getoptionvalue("filename").c_str();
    FILE * fp = fopen(fname, "r");
    if( fp != NULL ){ // file exists
      fprintf(stderr, "Error: File '%s' already exists and cannot be overwritten unless 'mode=overwrite' is specified.\n", 
          fname);
      throw cgcmexception("Error: Cannot write to output file.", -23);
      return;	
    }
  }
  bool fmt_netcdf = options.getoptionvalue("format") == "netcdf";
  bool fmt_gnuplot = options.getoptionvalue("format") == "gnuplot";
  bool fmt_matlab = options.getoptionvalue("format") == "matlab";
  if( ! fmt_netcdf && ! fmt_gnuplot && ! fmt_matlab ){
    throw cgcmexception("Error: Unrecognized file format was specified in options to -computestaticpart.", -25);
    return;
  }
  if(!options.isoptionset("norun")){
    try {
      //const REAL twopisquared = 39.4784176;
      const REAL R_coeff = options.isoptionset("R_coeff") ? options.rcoeff : -8.0; ///(twopisquared);
      const REAL S_coeff = options.isoptionset("S_coeff") ? options.scoeff :  4; ///(twopisquared);
      // if desired redirect the io stream for static computations
      if(nohup_flag) redirectoutputstreamsfornohup();
      computestaticpart(mo, Rs, Ss, Amap, Qmap, QImap,  kpqintegrand, asymkpqintegrand, computeP,computeQ, computeA, R_coeff, S_coeff);
    }catch( const ParserError & e){
      fprintf(stderr, "%s\n", e.what());
      throw cgcmexception("Error occured during computation of static weights.\n", -15);
    }
  }
  if( fmt_netcdf ){
    if(!nc_write_static_coeffs(mo, options.getoptionvalue("filename").c_str(), overwrite, Rs, Ss, Amap, Qmap, QImap)){
      throw cgcmexception("Error writing static part to netcdf file.", -15);
    }
  }else if( fmt_gnuplot ){
    if( !gnuplot_write_static_coeffs(mo, options.getoptionvalue("filename").c_str(), overwrite, Rs, Ss)){
      throw cgcmexception("Error writing static part to gnuplot file.", -15);
    } 
  }else if( fmt_matlab ){
    if( !mx_write_static_coeffs(mo, options.getoptionvalue("filename").c_str(), overwrite, Rs, Ss, Amap, Qmap, QImap)){
      throw cgcmexception("Error writing static part to matlab file.", -15);
    }
  }
  delete Rs;
  delete Ss;
  delete Amap;
  delete Qmap;
  delete QImap;
  fprintf(stdout, "Static computation completed.\n");
  exit(0);
};
// =================================================================================================
void flag_setnohup(const unsigned int argc, const char * argv [], unsigned int & index){
  // do some user friendly testing and reporting here
  model_class & mo = getModel();
  FILE * tester = NULL;
  const char * olist[3] = { "stdout", "stderr", "stdin" };
  const char * flist[3] = { "nohup.stdout", "nohup.stderr", "nohup.stdin" };
  if( mo.get_default_stdout().size() > 0 ){
    flist[0] = mo.get_default_stdout().c_str();
  }else{
    mo.set_default_stdout( flist[0] );
  }
  if( mo.get_default_stdin().size() > 0 ){
    flist[2] = mo.get_default_stdin().c_str();
  }else{
    mo.set_default_stdin( flist[2] );
  }
  fprintf(stderr, "Integrator will ignore SIGHUP signals.\n");
  for(int i = 0; i < 3; i++){
    tester = fopen(flist[i], "r");
    if(tester){ // file exists
      fprintf(stderr, "'%s' exists and will be appended to.\n", flist[i]);
      fclose(tester);
    }
    fprintf(stderr, "%s will be redirected to './%s'.\n", olist[i], flist[i]);
  }
  nohup_flag = true;
};
// =================================================================================================
void flag_setstdmsglabel(const unsigned int argc, const char * argv [], unsigned int & index){
  // takes 1 argument only!
  if(index + 1 >= argc || argv[index + 1][0] == '-'){
    fprintf(stderr, "Flag -stdmsg takes 1 argument.\n");
    return;
  }
  getModel().set_default_stdmsg(argv[index+1]);
  index++;
};
// =================================================================================================
void flag_setstdinlabel(const unsigned int argc, const char * argv [], unsigned int & index){
  // takes 1 argument only!
  if(index + 1 >= argc || argv[index + 1][0] == '-'){
    fprintf(stderr, "Flag -stdin takes 1 argument.\n");
    return;
  }
  getModel().set_default_stdin(argv[index+1]);
  index++;
};
// =================================================================================================
void flag_setstdoutlabel(const unsigned int argc, const char * argv [], unsigned int & index){
  // takes 1 argument only!
  if(index + 1 >= argc || argv[index + 1][0] == '-'){
    fprintf(stderr, "Flag -stdout takes 1 argument.\n");
    return;
  }
  getModel().set_default_stdout(argv[index+1]);
  index++;
};
// =================================================================================================
void flag_setdebugswitch(const unsigned int argc, const char * argv [], unsigned int & index){
  model_class & model = getModel();
  model.compile();
  model.setdebugonerror();
};
// =================================================================================================
void flag_define(const unsigned int argc, const char * argv [], unsigned int & index){
  string defineexpr;
  index++;
  while(index < argc){
    if(argv[index][0]=='-') { index --; break; }
    defineexpr += string(" ") + string(argv[index]);
    index++;
  }
  getModel().addmacrolist(defineexpr);
};
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
void flag_readparam(const unsigned int argc, const char * argv[], unsigned int & index){
  // takes 1 argument a param name
  if(index + 1 >= argc || argv[index + 1][0] == '-'){
    fprintf(stderr, "Flag -readparam takes 1 argument.\n");
    return;
  }
  model_class & mo = getModel();
  mo.compile();
  const char * param = argv[index+1];
  if( mo.does_param_exist(param) ){
    const parameter & p = mo.get_global_param_by_name(param);
    if(p.getType() == parameter::Real){
      printf("%f\n", p.getfValue());
    }else if(p.getType() == parameter::Integer){
      printf("%i\n", p.getiValue());
    }else{
      printf("%s\n", p.getsValue());
    }
  }
  index++;
};
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
void flag_restore_model_state(const unsigned int argc, const char * argv [], unsigned int & index){
  if(index +1 >=  argc || argv[index+1][0] == '-'){
    fprintf(stderr, "Flag -restore takes 1 argument.\n");
    return;
  }
  getModel().nc_restore_model_state_from_file( argv[index+1] );
  index++;
};
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
void flag_runscript(const unsigned int argc, const char * argv [], unsigned int & index){
  if(index + 1 >= argc || argv[index + 1][0] == '-'){
    fprintf(stderr, "Flag -runscript takes 1 argument, the script name.\n");
    return;
  }
  vector<const char *> script_list;
  index++;
  while( index < argc && argv[index][0] != '-' ){
    script_list.push_back(argv[index]);
    index++;
  }
  getModel().runscript(script_list);
  index --;
};
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
void flag_namelist(const unsigned int argc, const char * argv [], unsigned int & index){
  // takes 1 argument only!
  if(index + 1 >= argc || argv[index + 1][0] == '-'){
    fprintf(stderr, "Flag -namelist takes 1 argument.\n");
    return;
  }
  getModel().set_default_namelist(argv[index+1]);
  index++;
}
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
int evalexpr(const string & strExpr){
  ParserX P(pckCOMMON);
  token_list expr;
  extract_words(strExpr, expr);
  P.SetExpr(expr);
  try{
    int ival = cast_to_int(P.Eval());
    return ival;
  }catch(const ParserError & e){
    fprintf(stderr, "Evaluation error: '%s'.\n", e.what());
    return -1;
  }
}
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
int getseries(const vector<token> & args, size_t & index, vector<int> & options_list){
  // auxilary function reads expressions of the form opt = 1,2,3,4,5; until semicolon is reached
  if(args[index].word != "="){
    fprintf(stderr, "Option '%s' must be followed with an '=' assignment statement.\n", args[index-1].c_str());
    return -1;
  }
  index++; //march past = operator
  string expr;
  bool insidebrace = false;
  while(index < args.size()){
    if( !insidebrace && args[index].word == ","){
      break;
    }
    else if(args[index].word == "["){
      if(insidebrace){
        fprintf(stderr, "Unexpected '[' after assignment in display options.\n");
        return -1;
      }
      insidebrace = true;
    }
    else if(args[index].word == "]"){
      if(!insidebrace){
        fprintf(stderr, "Unexpected ']' without opening '[' in display options.\n");
        return -1;
      }
      insidebrace = false;
      int val = evalexpr(expr);
      if(val < 0){ return -1; };
      options_list.push_back(val);
      expr.clear();
    }
    else if(insidebrace && args[index].word == ","){
      int val = evalexpr(expr);
      if(val < 0){ return -1; }
      options_list.push_back(val);
      expr.clear();
    }else if(args[index].word == ";"){ //ignore

    }else{
      expr+=args[index].word;
    }
    index++;
  }
  // if we are at the end of the list
  if(expr.size() > 0){
    int val = evalexpr(expr);
    if(val < 0){ return -1; }
    options_list.push_back(val);
  }
  return 0;
}
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
int readoptions(const vector<token> & args, intoutputinfo & disp_opt){
  for(size_t i = 0; i < args.size(); i++){
    if(args[i].word == "displayphi" || args[i].word == "phi" || args[i].word == "dphi"){
      disp_opt.displayphi = true;
    }else if(args[i].word == "QL" || args[i].word == "etSsysonly" || args[i].word == "equaltimeSsysonly"){
      disp_opt.nouetssys = true;
    }else if(args[i].word == "noSsys" || args[i].word == "noSsystem"){
      disp_opt.nosys = true;
    }else if(args[i].word == "rundiagnostics" || args[i].word == "rundiags"){
      disp_opt.rundiagnosticsblock = true;
    }else if(args[i].word == "norundiagnostics" || args[i].word == "nodiags"){
      disp_opt.rundiagnosticsblock = false;
    }else if(args[i].word == "tprimelist"){
      i++;
      if(getseries(args, i, disp_opt.tprimeintegrations) < 0) return -1; 
    }else if(args[i].word == "dLOP" || args[i].word == "dLOPS" || args[i].word == "dLOPs"){
      disp_opt.displayLOP = true;
    }else if(args[i].word == "dP" || args[i].word == "dispP" || args[i].word == "dispPsys"){
      disp_opt.displayPsys = true;
    }else if(args[i].word == "noP" || args[i].word == "noPsys" || args[i].word == "noPsystem"){
      disp_opt.displayPsys = false;
    }else if(args[i].word == "equaltime" || args[i].word == "et"){
      disp_opt.phidisplayoptions.equaltimeonly = true;
    }else if(args[i].word == "toffset" || args[i].word == "i" 
        || args[i].word == "j" || args[i].word == "ki" || args[i].word == "nki"){ 
      if(i+1 >= args.size() || args[i+1].word != "="){
        fprintf(stderr, "Option '%s' must be followed with an '=' assignment statement.\n", args[i].word.c_str());
        return -1;
      }
      list<INDEX> * indexlist = NULL;
      if(args[i].word == "toffset") indexlist = &(disp_opt.phidisplayoptions.t_offset_indices);
      if(args[i].word == "i") indexlist = &(disp_opt.phidisplayoptions.i_indices);
      if(args[i].word == "j") indexlist = &(disp_opt.phidisplayoptions.j_indices);
      if(args[i].word == "ki") indexlist = &(disp_opt.phidisplayoptions.k_indices);
      if(args[i].word == "nki") indexlist = &(disp_opt.phidisplayoptions.m_indices);
      i++; // move position to the = sign
      vector<int> options;
      if(getseries(args, i, options)<0){ return -1; };
      for(size_t xx = 0; xx < options.size(); xx++){
        indexlist->push_back( static_cast<INDEX>(options[xx]) );
      }
    }else if(args[i].word == "t" || args[i].word == "timefreq" || args[i].word == "tfreq"
        || args[i].word == "tprime" || args[i].word == "tprimefreq" || args[i].word == "t'freq"){
      bool tfreq = args[i].word == "t" || args[i].word =="timefreq" || args[i].word == "tfreq" ;
      if(i+2 >= args.size() || args[i+1].word != "="){
        fprintf(stderr, "Option '%s' must be followed with an '=' assignment statement.\n", args[i].word.c_str());
        return -1;
      }
      i++;
      vector<int> options;
      if(getseries(args, i, options)<0) { return -1; }
      if(options.size() > 1){
        fprintf(stderr, "Time frequency option take only 1 argument.\n");
        return -1;
      }
      if(tfreq){
        disp_opt.phidisplayoptions.t_freq = options.back();
      }else{
        disp_opt.phidisplayoptions.t_prime_freq = options.back();
      }
    }else if(args[i].word == "," || args[i].word == ";"){ //ignore

    }else{
      fprintf(stderr, "Unexpected '%s' in options argument for flag -integrate.\n", args[i].word.c_str());
      return -1;
    }
  }
  return 0;
};
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
int setintoptions(intoutputinfo & disp_opt, unsigned int & index, unsigned int argc, const char * argv[]){
  string fullarg;
  while(index < argc){
    if(argv[index][0] == '-'){ index-=2; break; }
    string carg(argv[index]);
    fullarg+=carg;
    index++; 
  }
  fullarg+=string(";");
  vector<token> argoptions;
  preprocessor::tokenizestring(fullarg, argoptions);
  return readoptions(argoptions, disp_opt);
};
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
void flag_integrate(const unsigned int argc, const char * argv [], unsigned int & index){
  // takes number of time steps
  model_class & model = getModel();
  model.init_io(); //
  if(index + 1 >= argc || argv[index+1][0] == '-'){
    fprintf(stderr, "Flag -integrate takes at least 1 argument.\n");
    return;
  }
  int numsteps = 0;
  sscanf(argv[index+1], "%i", &numsteps);

  intoutputinfo disp_opt;
  disp_opt.outfile = model.getSTDOUT(); 
  disp_opt.displayphi = false;
  disp_opt.psysdisplayoptions.t_freq = 1;
  disp_opt.phidisplayoptions.t_freq = 1;
  disp_opt.phidisplayoptions.t_prime_freq = 1;
  disp_opt.nohup = nohup_flag;

  if(index+2 < argc){
    if(strcmp(argv[index+2], "-o") == 0){
      index+=3; // set position to -o
      if(setintoptions(disp_opt, index, argc, argv)<0){
        throw cgcmexception("Invalid arguments provided.\n", -11);
        return;
      }
    }
  }
  model.init_state(); // move to integration state
  pintegrate( model, numsteps, disp_opt); 
  index++;
}
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
void flag_printevalfuncs(const unsigned int argc, const char * argv [], unsigned int & index){
  // try to initialize the model first
  // init parser
  getModel().compile();
  ParserX p(pckNONE);
  getModel().assignparserdata(p, "v", false); 
  const fun_multimap & fmap = p.GetFunDef();
  for( fun_multimap::const_iterator item = fmap.begin(); item != fmap.end(); ++item){
    fprintf(stdout, "%s\n", item->second->AsICallback()->GetDesc());
  }
}
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
void flag_printlopinfo(const unsigned int argc, const char * argv [], unsigned int & index){
  model_class & mo = getModel();
  mo.compile();
  const spreprocdata & spdata = mo.getspreprocData();
  const vector<linearOPtranslationdata> & oplist = spdata.getOPdata();
  fprintf(stdout, "Printing linear operator compile data:\n");
  fprintf(stdout, "--------------------------------------\n");
  for(size_t iter = 0; iter < oplist.size(); iter++){ 
    const linearOPtranslationdata & opdata = oplist[iter];
    const char * name = opdata.OPname.c_str();
    int ang_mode = static_cast<int>(opdata.a_mode);
    fprintf(stdout, "OP# %i, name = %s\n", static_cast<int>(iter)+1, name);
    fprintf(stdout, "ang_mode_generic = %i\n", ang_mode);
    for(size_t x = 0; x < opdata.expressions.size(); x++){
      const rank3 & rpos = opdata.expressions[x].first;
      const token_list & opstring = opdata.expressions[x].second;
      fprintf(stdout, "m=%i, i=%i, j=%i  = [ %s ]\n", static_cast<int>(rpos[0]), 
          static_cast<int>(rpos[1])+1,
          static_cast<int>(rpos[2])+1,
          convert_expr_to_string(opstring).c_str());
    }
  }
}
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
void flag_compile(const unsigned int argc, const char * argv [], unsigned int & index){
  getModel().compile();
};
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
void flag_printuserfuncs(const unsigned int argc, const char * argv [], unsigned int & index){
  model_class & mo = getModel();
  mo.compile();
  vector< const model_diagnostics_block * > func_list;
  mo.getfunctiondiaglist( func_list ); // retrieve pointers to diag blocks that are functions
  for(size_t iter = 0; iter < func_list.size(); iter++){
    const model_diagnostics_block & func = *(func_list[iter]);
    vector<char_type> cstack = func.GetCallStackTypes();
    char_type rtype = func.GetReturnType();
    int numargs = func.getfuncnumargs();
    const char * name = func.getfuncname().c_str();
    fprintf(stdout,"%s %s(", get_type_id(rtype), name);
    for(int i = 0; i < numargs; i++){
      fprintf(stdout,"%s %s", get_type_id(cstack[i]), func.GetVariableName(i).c_str());
      if( i < numargs - 1) fprintf(stdout,", ");
    }
    fprintf(stdout,")\n");
  } 
};
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
void flag_printdiagfuncs(const unsigned int argc, const char * argv [], unsigned int & index){
  ParserX P(pckNONE);
  parser_define_basics(P, &getModel(), "v", false, true);
  assigndiagfuncs(P, false);
  const fun_multimap & functions = P.GetFunDef();
  fun_multimap::const_iterator item = functions.begin();
  for( ; item != functions.end(); ++item){
    fprintf(stdout, "%s\n", item->second.Get()->AsICallback()->GetDesc());
  }
}
// ------------------------------------------------------------------------------------------- //
void readenv(){
  // allows setting of namelist file
  char * enamelist = getenv("QGCM_NAMELIST");
  if(enamelist){
    getModel().set_default_namelist(enamelist);
  }
}
// ------------------------------------------------------------------------------------------- //
int main(const int argic, const char * argv[]){
  const unsigned int argc = static_cast<unsigned int>(argic);
  // first read environment variables used for the program, some options can be set
  // in the environment and on the command line. The latter takes precedence.
  readenv();
  vector< pair< const char *, flagprocessor > > flags;
  flags.push_back( pair<const char *, flagprocessor>( "-namelist", flag_namelist ) );
  flags.push_back( pair<const char *, flagprocessor>( "-define", flag_define ) );
  flags.push_back( pair<const char *, flagprocessor>( "-runscript", flag_runscript ) );
  flags.push_back( pair<const char *, flagprocessor>( "-runeval", flag_runparser ) );
  flags.push_back( pair<const char *, flagprocessor>( "-eval", flag_parse ) );
  flags.push_back( pair<const char *, flagprocessor>( "-printprofiles", flag_printprofiles ) );
  flags.push_back( pair<const char *, flagprocessor>( "-readparam", flag_readparam ) );
  flags.push_back( pair<const char *, flagprocessor>( "-printparams", flag_printparams ) );
  flags.push_back( pair<const char *, flagprocessor>( "-printfields", flag_printfields ) );
  flags.push_back( pair<const char *, flagprocessor>( "-printvariables", flag_printvariables ) );
  flags.push_back( pair<const char *, flagprocessor>( "-printfunctions", flag_printfunctions ) );
  flags.push_back( pair<const char *, flagprocessor>( "-printmacros", flag_printmacros ) );
  flags.push_back( pair<const char *, flagprocessor>( "-computestaticpart", flag_computestaticpart ) );
  flags.push_back( pair<const char *, flagprocessor>( "-integrate", flag_integrate ) );
  flags.push_back( pair<const char *, flagprocessor>( "-printevalfuncs", flag_printevalfuncs ) );
  flags.push_back( pair<const char *, flagprocessor>( "-printdiagfuncs", flag_printdiagfuncs ) );
  flags.push_back( pair<const char *, flagprocessor>( "-printuserfuncs", flag_printuserfuncs ) );
  flags.push_back( pair<const char *, flagprocessor>( "-debug", flag_setdebugswitch ) );
  flags.push_back( pair<const char *, flagprocessor>( "-stdmsg", flag_setstdmsglabel ) ); 
  flags.push_back( pair<const char *, flagprocessor>( "-stdin", flag_setstdinlabel ) ); 
  flags.push_back( pair<const char *, flagprocessor>( "-stdout", flag_setstdoutlabel ) ); 
  flags.push_back( pair<const char *, flagprocessor>( "-nohup", flag_setnohup ) );
  flags.push_back( pair<const char *, flagprocessor>( "-compile", flag_compile ) );
  flags.push_back( pair<const char *, flagprocessor>( "-printlopinfo", flag_printlopinfo) );
  flags.push_back( pair<const char *, flagprocessor>( "-restore", flag_restore_model_state) ); 
  // try{
  try{
    // try to initialize the model
    for(unsigned int i = 1; i < argc; i++){
      bool foundarg = false;
      for(unsigned int x = 0; x < flags.size(); x++){
        if(strcmp(flags[x].first, argv[i]) == 0){
          flags[x].second(argc, argv, i);
          foundarg = true;
          break;
        }
      }
      if(!foundarg){
        if(argv[i][0] == '-'){ // then we expect a flag, flag not recongized ...
          fprintf(stderr, "Warning: Unrecognized flag %s.\n", argv[i]);
          fprintf(stderr, "Valid flags include: \n");
          for(unsigned int x = 0; x < flags.size(); x++){
            fprintf(stderr, "\t%s\n", flags[x].first);
          } 
          return -1;
        }else{
          // we may want to run a script
          model_class & model = getModel();
          model.set_default_namelist(argv[i]);
          model.compile();
          model.init_state();
          intoutputinfo disp_opt;
          disp_opt.outfile = model.getSTDOUT();
          disp_opt.displayphi = false;
          disp_opt.psysdisplayoptions.t_freq = 1;
          disp_opt.phidisplayoptions.t_freq = 1;
          disp_opt.phidisplayoptions.t_prime_freq = 1;
          pintegrate(model, 0, disp_opt);
        }
      }
      foundarg = false;
    }
    // try to initialize the model if it has not already been done
    //fprintf(stdout, "Model has been initialized.\n");
  }catch(cgcmexception e){
    fprintf(stderr, "Exception msg: %s\n", e.what());
    return -1;
  }
  return 0;
}
